package com.authine.cloudpivot.web.api.controller;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.authine.cloudpivot.engine.api.model.organization.DepartmentModel;
import com.authine.cloudpivot.engine.api.model.organization.UserModel;
import com.authine.cloudpivot.engine.api.model.system.LoginLogModel;
import com.authine.cloudpivot.engine.api.utils.GenerateUtils;
import com.authine.cloudpivot.engine.enums.ErrCode;
import com.authine.cloudpivot.engine.enums.status.WorkItemStatus;
import com.authine.cloudpivot.engine.enums.type.LoginSourceType;
import com.authine.cloudpivot.engine.enums.type.OperationType;
import com.authine.cloudpivot.engine.enums.type.ProcessDataType;
import com.authine.cloudpivot.web.api.controller.base.BaseController;
import com.authine.cloudpivot.web.api.exception.PortalException;
import com.authine.cloudpivot.engine.api.facade.QueryLogsFacade;
import com.authine.cloudpivot.web.api.util.DateUtils;
import com.authine.cloudpivot.web.api.view.PageVO;
import com.authine.cloudpivot.web.api.view.ResponseResult;
import com.authine.cloudpivot.web.api.vo.QueryLogsVO;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.data.domain.*;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @ClassName QueryLogsController
 * @Description 日志查询
 * @author fengjie
 * @createTime 2022/4/20 10:39
 * @version 1.0.0
 */
@Api(tags = "运行时::日志查询")
@RestController
@RequestMapping("/api/ext/logs")
@Slf4j
public class QueryLogsController extends BaseController {
    @DubboReference(version = "1.00")
    private QueryLogsFacade queryLogsFacade;

    public static Map<String, Integer> loginSourceTypeMap = Maps.newHashMap();
    public static Map<String, String> workItemStatusMap = Maps.newHashMap();

    static {
        // 登录来源
        loginSourceTypeMap.put("全部", 0);
        loginSourceTypeMap.put("门户", 1);
        loginSourceTypeMap.put("钉钉平台", 2);
        loginSourceTypeMap.put("移动平台", 3);
        loginSourceTypeMap.put("其他", 4);

        // 流程状态
        workItemStatusMap.put("未打开", "NOTSTARTED");
        workItemStatusMap.put("处理中", "INPROGRESS");
        workItemStatusMap.put("已完成", "FINISHED");
        workItemStatusMap.put("被取消", "CANCELLED");
    }

    @PostMapping(value = "/query_logs")
    @ApiOperation(value = "根据视图编码获取日志列表")
    public ResponseResult<PageVO<Map<String, Object>>> queryLogs(@RequestBody QueryLogsVO queryDto) {
        log.info("queryLogs方法请求参数：{}", JSONObject.toJSONString(queryDto));
        if (ObjectUtil.isNull(queryDto.getPage()) || ObjectUtil.isNull(queryDto.getSize())) {
            throw new PortalException(ErrCode.SYS_PARAMETER_EMPTY);
        }
        this.validateCode(queryDto.getSchemaCode());
        this.validateCode(queryDto.getQueryCode());
        switch (queryDto.getQueryCode()) {
            case "logs":
                // 登录日志
                return searchLoginLogs(queryDto);
            case "business_operation_logs":
                // 业务操作日志
                return searchBusinessOperationLogs(queryDto, false);
            case "workflow_logs":
                // 流程日志
                return searchWorkFlowLogs(queryDto);
            case "business_access_logs":
                // 业务访问日志
                return searchBusinessOperationLogs(queryDto, true);
            default:
                throw new PortalException(ErrCode.SYS_PARAMETER_ERROR);
        }
    }

    /**
     * 根据查询条件获取登录日志
     * @param queryDto      查询条件
     * @return com.authine.cloudpivot.web.api.view.ResponseResult<com.authine.cloudpivot.web.api.view.PageVO < java.util.Map < java.lang.String, java.lang.Object>>>    分页后的数据
     * @Date 2022/4/20 15:47
     **/
    private ResponseResult<PageVO<Map<String, Object>>> searchLoginLogs(QueryLogsVO queryDto) {
        Integer page = queryDto.getPage();
        Integer size = queryDto.getSize();
        Map<String, Object> param = queryDto.getParam();
        Sort sort = new Sort(Sort.Direction.DESC, "loginTime");
        Pageable pageable = PageRequest.of(page == null ? 0 : page, size == null ? 20 : size, sort);

        // 获取登录来源，如果登录来源为空，设置为全部
        String loginSourceTypeStr = StringUtils.isEmpty(MapUtil.getStr(param, "loginSourceType")) ? LoginSourceType.ALL.getName() : MapUtil.getStr(param, "loginSourceType");

        // 获取登录日志列表
        Page<LoginLogModel> loginLogPage = getSystemManagementFacade()
                .queryLoginLog(MapUtil.getStr(param, "startTime"), MapUtil.getStr(param, "endTime"), LoginSourceType.get(loginSourceTypeMap.get(loginSourceTypeStr)),
                        MapUtil.getStr(param, "loginName"), MapUtil.getStr(param, "ipAddress"), pageable);
        // 设置返回结果
        List<Map<String, Object>> resultList = Lists.newArrayList();
        if (loginLogPage.getTotalElements() > 0L) {
            loginLogPage.getContent().forEach(loginLog -> {
                Map<String, Object> resultMap = JSONObject.parseObject(JSONObject.toJSONString(loginLog, SerializerFeature.WriteMapNullValue));
                // 新建表单时name为系统字段，导致字段冲突，所以需要做下适配
                resultMap.put("loginName", loginLog.getName());
                // 设置操作人部门
                UserModel userModel = getOrganizationFacade().getUserByUsername(loginLog.getUsername());
                // 如果是外部用户，操作人部门设置为空
                if (userModel == null) {
                    resultMap.put("loginDepartment", "");
                } else {
                    DepartmentModel department = getOrganizationFacade().getDepartment(userModel.getDepartmentId());
                    resultMap.put("loginDepartment", department.getName());
                }
                resultMap.put("loginSourceType", loginLog.getLoginSourceType().getName());
                resultList.add(resultMap);
            });
        }
        Page<Map<String, Object>> mapPage = new PageImpl<>(resultList, pageable, loginLogPage.getTotalElements());
        return this.getOkResponseResult(new PageVO<>(mapPage), "获取登录日志成功");
    }

    private ResponseResult<PageVO<Map<String, Object>>> searchWorkFlowLogs(QueryLogsVO queryDto) {
        Integer page = queryDto.getPage();
        Integer size = queryDto.getSize();
        Map<String, Object> param = queryDto.getParam();

        // 获取流程状态，如果流程状态为空，默认给处理中
        String status = MapUtil.getStr(param, "workItemStatus");
        WorkItemStatus workItemStatus = StringUtils.isEmpty(status) ? WorkItemStatus.INPROGRESS : WorkItemStatus.valueOf(workItemStatusMap.get(status));
        param.put("state", workItemStatus.name());
        switch (workItemStatus) {
            case DRAFT:
            case NOTSTARTED:
            case INPROGRESS:
                return queryWorkItemsOrFinishedWorkItems(page, size, param, false);
            case CANCELLED:
            case FINISHED:
                return queryWorkItemsOrFinishedWorkItems(page, size, param, true);
            default:
                throw new PortalException(ErrCode.SYS_PARAMETER_ERROR);
        }
    }

    /**
     * 根据条件查询业务日志
     * @param queryDto      查询条件
     * @param openSheet     是否打开表单
     * @return com.authine.cloudpivot.web.api.view.ResponseResult<com.authine.cloudpivot.web.api.view.PageVO < java.util.Map < java.lang.String, java.lang.Object>>>    分页后的数据
     * @Date 2022/4/20 17:08
     **/
    @SuppressWarnings("unchecked")
    private ResponseResult<PageVO<Map<String, Object>>> searchBusinessOperationLogs(QueryLogsVO queryDto, boolean openSheet) {
        Integer page = queryDto.getPage();
        Integer size = queryDto.getSize();
        Map<String, Object> param = queryDto.getParam();
        Sort sort = new Sort(Sort.Direction.DESC, "time");
        Pageable pageable = PageRequest.of(page == null ? 0 : page, size == null ? 20 : size, sort);
        Page<Map<String, Object>> mapPage;

        // 查询业务日志
        Map<String, Object> sqlParam = Maps.newHashMap();
        StringBuilder countSql = new StringBuilder("SELECT COUNT(*) FROM h_log_biz_object l inner join biz_workflow_instance w on w.id = l.workflowInstanceId inner join h_workflow_header h on w.workflowCode = h.workflowCode");
        StringBuilder querySql = new StringBuilder("SELECT l.id, l.id as bizObjectLogId, l.operateNode, l.operationType, l.time as operateTime, l.username as operateUser, l.workflowInstanceId, w.departmentName as operateDept, h.workflowName as businessModule FROM h_log_biz_object l" +
                " inner join biz_workflow_instance w on w.id = l.workflowInstanceId" +
                " inner join h_workflow_header h on w.workflowCode = h.workflowCode");
        StringBuilder conditionSql = new StringBuilder(" WHERE 1 = 1");
        conditionSql.append(" and w.dataType != :dataType ");
        sqlParam.put("dataType", ProcessDataType.SIMULATIVE.name());
        String orderSql = " order by l.time desc ";

        // 业务操作日志，查询除打开表单之外的操作；业务访问日志，只查询打开表单的操作
        if (openSheet) {
            conditionSql.append(" and l.operationType = :operationType");
        } else {
            conditionSql.append(" and l.operationType != :operationType ");
        }
        sqlParam.put("operationType", OperationType.OPEN_SHEET.name());

        // 操作人员
        if (!StringUtils.isEmpty(MapUtil.getStr(param, "operateUser")) && StringUtils.isEmpty(MapUtil.getStr(param, "operateDept"))) {
            conditionSql.append(" AND l.username = :operateUser ");
            sqlParam.put("operateUser", MapUtil.getStr(param, "operateUser"));
        }

        // 操作人部门
        if (!StringUtils.isEmpty(MapUtil.getStr(param, "operateDept"))) {
            // 通过部门id获取部门下所有人员
            String departmentId = MapUtil.getStr(param, "operateDept");
            List<UserModel> userModels = getOrganizationFacade().getUsersByDeptId(departmentId);
            if (!CollectionUtils.isEmpty(userModels)) {
                List<String> usernameList = userModels.stream().map(UserModel :: getName).collect(Collectors.toList());
                conditionSql.append(" AND l.username in (:usernameList) ");
                sqlParam.put("usernameList", usernameList);
            } else {
                conditionSql.append(" AND 1 = 2 ");
            }
        }

        // 操作时间
        Date dateTime;
        if (!StringUtils.isEmpty(MapUtil.getStr(param, "startTime"))) {
            String startTime = GenerateUtils.dateAppendTime(MapUtil.getStr(param, "startTime"), "00:00:00");
            dateTime = DateUtils.formateDateTime(startTime);
            if (dateTime != null) {
                conditionSql.append(" AND l.time >= :startDateTime ");
                sqlParam.put("startDateTime", dateTime);
            }
        }
        if (!StringUtils.isEmpty(MapUtil.getStr(param, "endTime"))) {
            String endTime = GenerateUtils.dateAppendTime(MapUtil.getStr(param, "endTime"), "00:00:00");
            dateTime = DateUtils.formateDateTime(endTime);
            if (dateTime != null) {
                conditionSql.append(" AND l.time <= :endDateTime ");
                sqlParam.put("endDateTime", dateTime);
            }
        }

        // 业务模块
        if (!StringUtils.isEmpty(MapUtil.getStr(param, "businessModule"))) {
            // 通过流程编码获取所有实例id
            String getInstanceIdListSql = "SELECT id FROM biz_workflow_instance where workflowCode = :workflowCode";
            Map<String, Object> getInstanceIdListSqlParam = Maps.newHashMap();
            getInstanceIdListSqlParam.put("workflowCode", MapUtil.getStr(param, "businessModule"));
            Pageable getInstanceIdListPageable = PageRequest.of(0, Integer.MAX_VALUE, sort);
            List<Map<String, Object>> queryList = (List<Map<String, Object>>) this.queryLogsFacade.getList(getInstanceIdListSql, getInstanceIdListPageable, getInstanceIdListSqlParam);
            if (CollectionUtils.isEmpty(queryList)) {
                mapPage = new PageImpl<>(Lists.newArrayList(), pageable, 0L);
                return this.getOkResponseResult(new PageVO<>(mapPage), "获取业务日志成功");
            } else {
                conditionSql.append(" AND l.workflowInstanceId in (:workflowInstanceIdList) ");
                List<String> workflowInstanceIdList = queryList.stream().map(map -> map.get("id")).map(String::valueOf).collect(Collectors.toList());
                sqlParam.put("workflowInstanceIdList", workflowInstanceIdList);
            }

        }
        String finalQuerySql = querySql.append(conditionSql).append(orderSql).toString();
        String finalCountSql = countSql.append(conditionSql).toString();
        int count = this.queryLogsFacade.getCount(finalCountSql, sqlParam);
        if (count == 0) {
            mapPage = new PageImpl<>(Lists.newArrayList(), pageable, 0L);
        } else {
            List<Map<String, Object>> resultList = (List<Map<String, Object>>) this.queryLogsFacade.getList(finalQuerySql, pageable, sqlParam);
            resultList.forEach(resultMap ->
                    resultMap.put("operationType", OperationType.valueOf(MapUtil.getStr(resultMap, "operationType")).getName())
            );
            mapPage = new PageImpl<>(resultList, pageable, count);
        }
        return this.getOkResponseResult(new PageVO<>(mapPage), "获取业务日志成功");
    }

    /**
     * 查询待办或已办任务
     * @param page          当前页数
     * @param size          每页大小
     * @param param         查询条件
     * @param finished      是否已完成
     * @return com.authine.cloudpivot.web.api.view.ResponseResult<com.authine.cloudpivot.web.api.view.PageVO < java.util.Map < java.lang.String, java.lang.Object>>>    分页数据
     * @Date 2022/4/21 10:58
     **/
    private ResponseResult<PageVO<Map<String, Object>>> queryWorkItemsOrFinishedWorkItems(Integer page, Integer size, Map<String, Object> param, boolean finished) {
        Sort sort;
        String tableName;
        String orderSql;
        if (finished) {
            tableName = "biz_workitem_finished";
            sort = new Sort(Sort.Direction.DESC, "finishTime");
            orderSql = " order by t.finishTime desc ";
        } else {
            tableName = "biz_workitem";
            sort = new Sort(Sort.Direction.DESC, "startTime");
            orderSql = " order by t.startTime desc ";
        }
        Pageable pageable = PageRequest.of(page == null ? 0 : page, size == null ? 10 : size, sort);
        Page<Map<String, Object>> mapPage;

        // 查询待办任务日志
        Map<String, Object> sqlParam = Maps.newHashMap();
        StringBuilder querySql = new StringBuilder("SELECT t.id as workitemId, t.workflowCode, t.originator, t.participant, t.startTime as workflowTime, t.departmentId, t.departmentName as originatorDepartmentName, t.originatorName, t.participantName, w.sequenceNo as workflowSequenceNo, w.id as workflowInstanceId, w.bizObjectId, w.bizObjectId as id, h.workflowName" +
                " FROM " + tableName + " t" +
                " inner join biz_workflow_instance w on t.instanceId = w.id " +
                " inner join h_workflow_header h on w.workflowCode = h.workflowCode ");
        StringBuilder countSql = new StringBuilder("SELECT COUNT(1) FROM " + tableName + " t inner join biz_workflow_instance w on t.instanceId=w.id inner join h_workflow_header h on w.workflowCode = h.workflowCode ");
        StringBuilder conditionSql = new StringBuilder(" WHERE 1 = 1 ");
        conditionSql.append(" and w.dataType != :dataType ");
        sqlParam.put("dataType", ProcessDataType.SIMULATIVE.name());

        // 流程状态
        if (!StringUtils.isEmpty(MapUtil.getStr(param, "state"))) {
            conditionSql.append("  and t.state = :state ");
            sqlParam.put("state", MapUtil.getStr(param, "state"));
        }

        // 发起人
        if (!StringUtils.isEmpty(MapUtil.getStr(param, "originator"))) {
            conditionSql.append(" AND t.originator = :originator ");
            sqlParam.put("originator", MapUtil.getStr(param, "originator"));
        }

        // 流程名称
        if (!StringUtils.isEmpty(MapUtil.getStr(param, "workflowCode"))) {
            conditionSql.append(" AND t.workflowCode = :workflowCode ");
            sqlParam.put("workflowCode", MapUtil.getStr(param, "workflowCode"));
        }

        // 开始时间
        Date dateTime;
        if (!StringUtils.isEmpty(MapUtil.getStr(param, "startTime"))) {
            String startTime = GenerateUtils.dateAppendTime(MapUtil.getStr(param, "startTime"), "00:00:00");
            dateTime = DateUtils.formateDateTime(startTime);
            if (dateTime != null) {
                if (finished) {
                    conditionSql.append(" AND t.finishTime >= :startDateTime ");
                } else {
                    conditionSql.append(" AND t.startTime >= :startDateTime ");
                }
                sqlParam.put("startDateTime", dateTime);
            }
        }
        if (!StringUtils.isEmpty(MapUtil.getStr(param, "endTime"))) {
            String endTime = GenerateUtils.dateAppendTime(MapUtil.getStr(param, "endTime"), "00:00:00");
            dateTime = DateUtils.formateDateTime(endTime);
            if (dateTime != null) {
                if (finished) {
                    conditionSql.append(" AND t.finishTime <= :endDateTime ");
                } else {
                    conditionSql.append(" AND t.startTime <= :endDateTime ");
                }
                sqlParam.put("endDateTime", dateTime);
            }
        }
        String finalQuerySql = querySql.append(conditionSql).append(orderSql).toString();
        String finalCountSql = countSql.append(conditionSql).toString();
        int count = this.queryLogsFacade.getCount(finalCountSql, sqlParam);
        if (count == 0) {
            mapPage = new PageImpl<>(Lists.newArrayList(), pageable, 0L);
        } else {
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> resultList = (List<Map<String, Object>>) this.queryLogsFacade.getList(finalQuerySql, pageable, sqlParam);
            resultList.forEach(resultMap -> {
                // 设置处理人部门
                String userId = MapUtil.getStr(resultMap, "participant");
                UserModel user = getOrganizationFacade().getUser(userId);
                if (user != null) {
                    DepartmentModel department = getOrganizationFacade().getDepartment(user.getDepartmentId());
                    resultMap.put("participantDeptName", department.getName());
                } else {
                    resultMap.put("participantDeptName", "");
                }
            });
            mapPage = new PageImpl<>(resultList, pageable, count);
        }
        return this.getOkResponseResult(new PageVO<>(mapPage), "获取流程日志成功");
    }
}
